#!/bin/sh
# Regression test for winetricks
#
# Copyright (C) 2010-2014 Dan Kegel
# Copyright (C) 2011-2017 Austin English
# See also copyright notice in src/winetricks.
#
# This software comes with ABSOLUTELY NO WARRANTY.
#
# This is free software, placed under the terms of the GNU Lesser
# Public License version 2.1 (or later), as published by the Free
# Software Foundation. Please see the file COPYING for details.

# Requires wine >= 2.8 (https://bugs.winehq.org/show_bug.cgi?id=37811)

# TODO:
# add selfupdate tests
# automate non -q verbs with autohotkey

# Override this if you want to put the work area on a different disk
WINE_PREFIXES=${WINE_PREFIXES:-${HOME}/winetrickstest-prefixes}
XDG_CACHE_HOME="${XDG_CACHE_HOME:-${HOME}/.cache}"
cache="${XDG_CACHE_HOME}/winetricks"

#set -e
set -x

if [ -f src/winetricks ] ; then
    TOP="${PWD}"
    shwinetricks="${PWD}/src/winetricks"
elif [ -f ../src/winetricks ] ; then
    # realpath isn't available on OSX, use a subshell instead:
    TOP="$(cd .. && echo "${PWD}")"
    shwinetricks="${TOP}/src/winetricks"
elif [ -f ../../src/winetricks ] ; then
    # realpath isn't available on OSX, use a subshell instead:
    TOP="$(cd ../.. && echo "${PWD}")"
    shwinetricks="${TOP}/src/winetricks"
else
    echo "Running from unknown directory. Exiting"
    exit 1
fi

# Start with a fresh output dir each time:
outputdir="${TOP}/output"
rm -rf "${outputdir}"
mkdir -p "${outputdir}"

# Workaround for:
# kcov has trouble with forking shell scripts
# https://github.com/SimonKagstrom/kcov/issues/64
# https://github.com/SimonKagstrom/kcov/issues/165

# check-deps checks for time/cabextract, but doesn't invoke winetricks so we don't care about it for coverage
# If the below code runs, it creates an empty 'kcov-results' dir in the root directory. So, bail out here:
if [ "$1" = "check-deps" ] ; then
    true

# Using an environmental variable rather than a CLI option
# so it doesn't need extra handling/targets in Makefile
elif [ -n "${WINETRICKS_ENABLE_KCOV}" ] ; then
    KCOV_RESULTS="${outputdir}/kcov-results"
    rm -rf "${KCOV_RESULTS}"
    mkdir -p "${KCOV_RESULTS}"

    # kcov --kcov-options output-dir testscript --test-script-args
    # I think kcov lets you append results, if not we may have to use separate for each invocation then combine at the end
    # It seems to be capping out at 468 lines for me. Mentioned on their github here:
    # https://github.com/SimonKagstrom/kcov/issues/165

    # Previously, winetricks-test used `$WINETRICKS`. That fails for kcov, which
    # then tracks bash itself and not winetricks. `${shwinetricks}` works fine.
    WINETRICKS="kcov --configure=bash-use-basic-parser=1 ${KCOV_RESULTS} \"${shwinetricks}\""
    #WINETRICKS="kcov --configure=bash-use-basic-parser=1 $KCOV_RESULTS-$(date +%k-%M-%S) \"${shwinetricks}\""
else
    WINETRICKS="${shwinetricks}"
fi

# Disable winetricks update checks:
WINETRICKS_LATEST_VERSION_CHECK=disabled
export WINETRICKS_LATEST_VERSION_CHECK

# Disable some verbose output that makes parsing harder:
WINETRICKS_SUPER_QUIET=1
export WINETRICKS_SUPER_QUIET

# verbs known to not work in -q mode yet
BLACKLIST="dx8sdk|kde|psdk2003|psdkwin7"
# verbs that hang in -q because of simple problem we should work around soon
BLACKLIST="${BLACKLIST}|vc2005trial"
# verbs that are too slow
BLACKLIST="${BLACKLIST}|dxsdk_nov2006|dxsdk_jun2010"
# broken/flaky, http://bugs.winehq.org/show_bug.cgi?id=26016
BLACKLIST="${BLACKLIST}|xmllite"
# redundant metaverbs
BLACKLIST="${BLACKLIST}|allcodecs|allfonts|cjkfonts|pptfonts"
# https://bugs.winehq.org/show_bug.cgi?id=16876
BLACKLIST="${BLACKLIST}|wmi"
# These break in combination with sandbox, https://bugs.winehq.org/show_bug.cgi?id=49421
BLACKLIST="${BLACKLIST}|ffdshow|python26|python27"
# Also sandbox, but different bug, https://bugs.winehq.org/show_bug.cgi?id=49550
BLACKLIST="${BLACKLIST}|dirac"
# https://bugs.winehq.org/show_bug.cgi?id=50061 / https://github.com/Winetricks/winetricks/issues/1644
BLACKLIST="${BLACKLIST}|quicktime76"

# Travis CI is apparently blocking archive.org, which breaks several verbs:
# https://github.com/travis-ci/travis-ci/issues/6798
# Blacklist those verbs on those hosts:
# FIXME: setup another test target for torify, use it to d/l these?
ARCHIVE_ORG_BLACKLIST="atmlib|avifil32|cabinet|cmd|comctl32ocx|comdgl32ocx|gmdls|mdac27|mfc40|msflxgrd|mshflxgd"
ARCHIVE_ORG_BLACKLIST="${ARCHIVE_ORG_BLACKLIST}|msls31ms|msmask|msscript|msxml3|pdh|pngfilt|riched30"
ARCHIVE_ORG_BLACKLIST="${ARCHIVE_ORG_BLACKLIST}|richtx32|secur32|tabctl32|usp10|"

# See https://docs.travis-ci.com/user/environment-variables/#Default-Environment-Variables
if [ "${TRAVIS}" = "true" ] ; then
    BLACKLIST="${ARCHIVE_ORG_BLACKLIST}|${BLACKLIST}"
fi

# Tests that fail under Xvfb
XVFB_DOTNET_BLACKLIST="dotnet11|dotnet11sp1|dotnet20|dotnet20sdk|dotnet20sp1|dotnet30|dotnet40|dotnet46"
XVFB_BLACKLIST="${XVFB_DOTNET_BLACKLIST}|binkw32|dirac|directmusic|dxdiag|gdiplus_winxp|gfw|ie6|ie7|ie8"
XVFB_BLACKLIST="${XVFB_BLACKLIST}|jet40|nuget|quicktime72|vcrun2008|vcrun2010|vcrun2012|vcrun2013|vcrun2015"
XVFB_BLACKLIST="${XVFB_BLACKLIST}|vjrun20|windowscodecs|wmi|wmp9|wmp10|wsh56js|wsh56vb|xmllite|xna31|xna40|xvid"

failed_verbs=""
passes=0
errors=0
skips=0

# Check for programs this script (or winetricks) uses.
# Better to find out they're missing now than in the
# middle of a two day run.
check_deps() {
    for tool in time cabextract; do
        command -v "${tool}" >/dev/null 2>&1
        ret=$?

        if [ ! ${ret} -eq 0 ] ; then
            echo "Please install ${tool}."
            exit 1
        fi
    done
}

fail()
{
    echo "FAIL: $*"
    failed_verbs="${failed_verbs}|$*"
    errors=$((errors + 1))
    w_prefix_failed=1
    status
}

pass()
{
    echo "PASS: $*"
    passes=$((passes + 1))
    status
}

skip()
{
    echo "SKIP: $*"
    skipped_verbs="${skipped_verbs}|$*"
    skips=$((skips + 1))
    was_skipped=1
    status
}

status()
{
    echo "Test in progress. Current status: ${errors} failures, ${passes} successes, ${skips} skipped."
}

w_die()
{
    echo "$*"
    exit 1
}

w_remove_prefix()
{
    if [ -n "${WINEPREFIX}" ]; then
        # Cleanup the prefix, unless $W_OPT_NOCLEAN is set to 1 (to match winetricks)
        # AND if there weren't failures to debug:
        if [ "${W_OPT_NOCLEAN}" = "1" ]; then
            echo "W_OPT_NOCLEAN set to 1, not removing wineprefix ${WINEPREFIX}"
        elif [ "${w_prefix_failed}" = "1" ]; then
            echo "w_prefix_failed set to 1, not removing wineprefix ${WINEPREFIX}"
        elif [ -n "${w_remove_prefix_disable}" ]; then
            # Don't remove the prefix yet, as there will be some further checks on the same prefix
            echo "prefix removed disabled, not removing wineprefix ${WINEPREFIX}"
        else
            echo "Removing wineprefix ${WINEPREFIX}"
            rm -rf "${WINEPREFIX}"
        fi
    fi
}

w_time()
{
    # OSX time doesn't support -o, so try it first:
    if ! /usr/bin/time -o "${outputdir}/test.log" echo test > /dev/null 2>&1; then
        /usr/bin/time -p "$@"
    else
        /usr/bin/time -p -o "${outputdir}/time.log" "$@"
    fi
}

case "${LANG}" in
    ""|"C") echo "Some games won't install in the Posix locale; doing 'export LANG=en_US.UTF-8'" ; export LANG=en_US.UTF-8;;
esac

case "${OS}" in
    "Windows_NT")
        # Mostly unimplemented...
        # Cheezy fix for getting rid of double slashes when running cygwin in wine
        case "${HOME}" in
            /) HOME="" ;;
        esac

        WINE=""
        WINESERVER=true
        #DRIVE_C="C:/"
        ;;
    *)
        export WINE=${WINE:-wine}
        # Find wineserver.  Some distros (Debian) don't have it on the path,
        # on the mistaken understanding that user scripts never need it :-(
        # If wineserver is from wine-development set WINE to wine-development.
        # FIXME: get packagers to put wineserver on the path.
        for x in \
            "${WINESERVER}" \
            "${WINE}server" \
            "$(command -v wineserver 2> /dev/null)" \
            "$(dirname "${WINE}")/server/wineserver" \
            /usr/lib/wine/wineserver \
            /usr/lib/i386-kfreebsd-gnu/wine/wineserver \
            /usr/lib/i386-linux-gnu/wine/wineserver \
            /usr/lib/powerpc-linux-gnu/wine/wineserver \
            /usr/lib/i386-kfreebsd-gnu/wine/bin/wineserver \
            /usr/lib/i386-linux-gnu/wine/bin/wineserver \
            /usr/lib/powerpc-linux-gnu/wine/bin/wineserver \
            /usr/lib/x86_64-linux-gnu/wine/bin/wineserver \
            /usr/lib/i386-kfreebsd-gnu/wine-development/wineserver \
            /usr/lib/i386-linux-gnu/wine-development/wineserver \
            /usr/lib/powerpc-linux-gnu/wine-development/wineserver \
            /usr/lib/x86_64-linux-gnu/wine-development/wineserver \
            file-not-found; do
        if [ -x "${x}" ] ; then
            case "${x}" in
                /usr/lib/*/wine-development/wineserver)
                    if [ -x /usr/bin/wine-development ] ; then
                        WINE="/usr/bin/wine-development"
                    fi
                    ;;
            esac
            break
        fi
    done

    case "${x}" in
        file-not-found) w_die "wineserver not found!" ;;
        *) WINESERVER="${x}" ;;
    esac
    ;;
esac

test_speed()
{
    speed_test="$1"

    case "${speed_test}" in
        # No wine/WINEPREFIX needed:
        list|list-download|list-cached) export _W_wine_not_needed=1;;

        # Needs a WINEPREFIX, with some things installed (need at least 5, see list_lines below):
        # Note: not using test_command here because that would forcefully override WINEPREFIX
        list-installed)
            export WINEPREFIX="${WINE_PREFIXES}/test_speed-list-installed"
            w_remove_prefix_disable=1
            # shellcheck disable=SC2086
            ${XVFB} ${WINETRICKS} good good good good good || fail "\'${XVFB} ${WINETRICKS} good good good good good\' failed"
            ;;

        *) echo "error: unknown test_speed command '${speed_test}'"; exit 1;;
    esac

    # shellcheck disable=SC2086
    if ! w_time ${XVFB} ${WINETRICKS} "$1" > "${outputdir}/foo.log"; then
        fail "winetricks $1 returned status $?"
    else
        pass "winetricks $1 returned status $?"
    fi

    list_lines="$(wc -l < "${outputdir}/foo.log")"
    if [ "${list_lines}" -lt 5 ] ; then
        fail "winetricks $1 returned ${list_lines} lines, expected at least 5"
    else
        pass "winetricks $1 returned ${list_lines} lines"
    fi

    if [ ! -f time.log ] ; then
        # OSX, fake it:
        seconds=0
    else
        seconds=$(awk '/real/ {print $2}' < "${outputdir}/time.log" | sed 's/\..*//')
    fi

    echo "test_speed: winetricks $1 took ${seconds} seconds"
    # Longest runtime as of 11 Dec 2010 is 5 seconds on an e8400 with cygwin
    if [ "${seconds}" -gt 7 ] ; then
        fail "test_speed: winetricks $1 took ${seconds} seconds"
    else
        pass "test_speed: winetricks $1 took ${seconds} seconds"
    fi

    unset _W_wine_not_needed
    unset w_remove_prefix_disable
    w_remove_prefix
}

# test win32 7-zip fallback
test_7zip()
{
    # 7z
    WINETRICKS_FORCE_WIN_7Z=1 test_command --verify comctl32ocx

    # ar
    WINETRICKS_FORCE_WIN_7Z=1 test_command --verify opensymbol

    # rar
    # FIXME: protectionid requires a manual download, and the only other user is avatar_demo
    #WINETRICKS_FORCE_WIN_7Z=1 test_command --verify protectionid

    # zip
    WINETRICKS_FORCE_WIN_7Z=1 test_command --verify vb2run
}

# Return the number of blocks available in the system
total_df()
{
    df | grep -v /dev/loop | awk '/^\// { sum += $4 } END { print sum }'
}

# for single apps, wrapper around test_command:
test_app()
{
    app=$1
    was_skipped=0

    # Watch transient disk space
    DF_START=$(total_df)
    if [ -d "${W_CACHE}/${app}" ] ; then
        DU_CACHE_START=$(du -s "${W_CACHE}/${app}" | awk '{print $1}')
    else
        DU_CACHE_START=0
    fi
    touch "${outputdir}/df-daemon"
    (set +x; while test -f "${outputdir}/df-daemon"; do total_df; sleep 1; done ) > "${outputdir}/df-during.log" &

    test_command --verify "${app}"

    if [ ${was_skipped} = 1 ]; then
        skip "post-install file check for skipped verb ${app}"
    else
        # Post install:
        # Don't check whether metaverbs are installed
        case "${app}" in
            allcodecs) ;;
            *)
                # if test was skipped because of wrong arch, don't check for it:
                if [ "${archskip}" = "1" ]; then
                    skip "${app} was skipped because of wrong prefix arch!"
                    return
                fi

                # no xvfb needed
                DISPLAY="" "${shwinetricks}" -q list-installed > "${outputdir}/list-installed.out"

                if ! grep -w "${app}" "${outputdir}/list-installed.out"; then
                    fail "test app ${app} not installed after install?"
                fi

                ;;
        esac

        # Cleanup..
        echo "rm ${outputdir}/df-daemon"
        rm "${outputdir}/df-daemon"

        # Total max disk usage = max df change plus any initial blocks in cache
        DF_MIN=$(awk '{ if (min == "" || $1 < min) min=$1; } END {printf "%d\n", min}' < "${outputdir}/df-during.log" )
        DF_DIFF=$((DF_START - DF_MIN))
        TOTAL=$((DF_DIFF + DU_CACHE_START))
        echo "test_app: ${app}: max_disk ${TOTAL} blocks."
        TOTAL_MB=$((TOTAL / 1024))
        mkdir -p "${outputdir}/measurements"
        echo "${app}:size_MB=${TOTAL_MB},time_sec=${seconds}" >> "${outputdir}/measurements/${app}.dat"
    fi

    unset w_remove_prefix_disable
    w_remove_prefix
}

test_command()
{
    archskip=0
    command="$*"

    # will be set by fail() if a prefix has a failure, so that w_remove_prefix() doesn't remove the prefix
    unset w_prefix_failed

    # Hate to hardcode this, but not a good way to set it from the winetricks side:
    case "${command}" in
        "--verify bad") EXPECT_FAIL="yes";;
        *) :;;
    esac

    # If _W_wine_not_needed is set, don't fail if wine is missing (used by test_listing())

    # This previously changed spaces to hyphens. Going back to spaces so we test WINEPREFIXes
    # with special characters, to prevent issues like https://github.com/Winetricks/winetricks/issues/995
    export WINEPREFIX="${WINE_PREFIXES}/${command}"
    #DRIVE_C="$WINEPREFIX/dosdevices/c:"

    # always use a clean $WINEPREFIX
    if [ -d "${WINEPREFIX}" ] ; then
        rm -rf "${WINEPREFIX}"
    fi

    # Isolate us from the user's home directory
    # shellcheck disable=SC2086
    if [ -z "${_W_wine_not_needed}" ]; then
        # First, make the prefix (without $DISPLAY set).
        # This reduces the amount of GUI activity (unless the prefix actually makes a window, which most tests don't).
        DISPLAY="" "${WINE}" wineboot || fail "\'${WINE} wineboot\' failed"
        "${WINESERVER}" -w

        ${XVFB} ${WINETRICKS} sandbox || fail "\'${XVFB} ${WINETRICKS} sandbox\' failed"
        "${WINESERVER}" -w

        echo "Installing ${command}"
        ${XVFB} ${WINETRICKS} --no-isolate -q nocrashdialog "$@"
        return=$?
    else
        DISPLAY="" ${XVFB} ${WINETRICKS} --no-isolate -q "$@"
        return=$?
    fi

    if [ -d "${WINEPREFIX}/drive_c/windows/syswow64" ]; then
        WINEARCH="win64"
    elif [ -d "${WINEPREFIX}/drive_c/windows/system32" ]; then
        WINEARCH="win32"
    else
        # This happens during, e.g., `winetricks apps list`.
        echo "No WINEPREFIX found, not setting WINEARCH"
    fi

    if [ "${return}" = "32" ] && [ "${WINEARCH}" != "win32" ]; then
        skip "${command} is not supported on ${WINEARCH}, requires win32"
        archskip=1
        return
    elif [ "${return}" = "64" ] && [ "${WINEARCH}" != "win64" ]; then
        skip "${command} is not supported on ${WINEARCH}, requires win64"
        archskip=1
        return
    elif [ "${return}" = "99" ]; then
        skip "${command} is known to be broken on this wine version, ignoring"
        return
    fi

    if [ "${EXPECT_FAIL}" = "yes" ] ; then
        # A success is failure:
        if [ "${return}" = "0" ] ; then
            fail "${command} succeeded, should have failed"
        else
            pass "test_command ${command} expected to fail, and did fail!"
        fi
    else
        if [ "${return}" = "0" ] ; then
            pass "$@"
        else
            fail "$@"
        fi
    fi

    if [ ! -f time.log ] ; then
        seconds=0
    else
        seconds=$(awk '/real/ {print $2}' < time.log | sed 's/\..*//')
    fi

    echo "test_command: ${app}: install_time ${seconds} seconds."

    echo "Checking for dangling processes!"
    # shellcheck disable=SC2009
    ps augxw | grep \\.exe | grep -v grep

    if [ -z "${_W_wine_not_needed}" ]; then
        "${WINESERVER}" -w
        echo "Wineserver done."
    fi

    w_remove_prefix

    unset EXPECT_FAIL
}

test_custom_verbs()
{
    # Custom .verb support isn't commonly used, and may break without notice for a while
    # Also try with --isolate:
    # https://github.com/Winetricks/winetricks/issues/599

    # Test as apps first, then dll, since they take different codepaths

    # First, a working 'app' as an app:
cat > "${outputdir}/true.verb" <<_EOF
w_metadata true apps

load_true()
{
    echo "true should succeed"
    /bin/true
}

_EOF

    # Next, a broken 'app' as an app:
cat > "${outputdir}/false.verb" <<_EOF
w_metadata false apps

load_false()
{
    echo "false should fail"
    false
}

_EOF

    # no xvfb needed
    ${WINETRICKS} --no-isolate "${outputdir}/true.verb" ; ret=$?
    case ${ret} in
        0) pass "${outputdir}/true.verb not isolated, as apps passed" ;;
        *) fail "${outputdir}/true.verb not isolated, as apps failed" ;;
    esac

    # no xvfb needed
    ${WINETRICKS} --no-isolate "${outputdir}/false.verb" ; ret=$?
    case ${ret} in
        0) fail "false.verb not isolated, as apps worked, should have failed" ;;
        1) pass "false.verb not isolated, as apps passed" ;;
        *) fail "false.verb not isolated, as apps failed in unexpected way" ;;
    esac

    # no xvfb needed
    ${WINETRICKS} --isolate "${outputdir}/true.verb" ; ret=$?
    case ${ret} in
        0) pass "${outputdir}/true.verb isolated, as apps passed" ;;
        *) fail "${outputdir}/true.verb isolated, as apps failed" ;;
    esac

    # no xvfb needed
    ${WINETRICKS} --isolate "${outputdir}/false.verb" ; ret=$?
    case ${ret} in
        0) fail "${outputdir}/false.verb isolated, as apps worked, should have failed" ;;
        1) pass "${outputdir}/false.verb isolated, as apps passed" ;;
        *) fail "${outputdir}/false.verb isolated, as apps failed in unexpected way" ;;
    esac

    # Repeat as dll:

    # First, a working 'app' as a dll:
cat > "${outputdir}/true.verb" <<_EOF
w_metadata true dlls

load_true()
{
    echo "true should succeed"
    true
}
_EOF

    # Next, a broken 'app' as a dll:
cat > "${outputdir}/false.verb" <<_EOF
w_metadata false dlls

load_false()
{
    echo "false should fail"
    false
}
_EOF

    # no xvfb needed
    ${WINETRICKS} --no-isolate "${outputdir}/true.verb" ; ret=$?
    case ${ret} in
        0) pass "true.verb isolated, as dlls passed" ;;
        *) fail "true.verb isolated, as dlls failed" ;;
    esac

    # no xvfb needed
    ${WINETRICKS} --no-isolate "${outputdir}/false.verb" ; ret=$?
    case ${ret} in
        0) fail "${outputdir}/false.verb isolated, as dlls worked, should have failed" ;;
        1) pass "${outputdir}/false.verb isolated, as dlls passed" ;;
        *) fail "${outputdir}/false.verb isolated, as dlls failed in unexpected way" ;;
    esac

    # no xvfb needed
    ${WINETRICKS} --no-isolate "${outputdir}/true.verb" ; ret=$?
    case ${ret} in
        0) pass "${outputdir}/true.verb isolated, as dlls passed" ;;
        *) fail "${outputdir}/true.verb isolated, as dlls failed" ;;
    esac

    # no xvfb needed
    ${WINETRICKS} --no-isolate "${outputdir}/false.verb" ; ret=$?
    case ${ret} in
        0) fail "${outputdir}/false.verb isolated, as dlls worked, should have failed" ;;
        1) pass "${outputdir}/false.verb isolated, as dlls passed" ;;
        *) fail "${outputdir}/false.verb isolated, as dlls failed in unexpected way" ;;
    esac

    rm "${outputdir}/false.verb" "${outputdir}/true.verb"
    pass
}

test_category()
{
    # Test everything list in a particular category (i.e., dlls/fonts/settings/etc.)
    category="$1"

    if [ -z "$1" ]; then
        fail "You must specify a category to test. I.e., one of: $(winetricks list | tr '\n' ' ')"
    fi

    # no xvfb needed, kcov breaks
    "${shwinetricks}" list-manual-download > "${outputdir}/manual.log"
    "${shwinetricks}" "${category}" list | awk '{print $1}' > "${outputdir}/${category}.log"
    if grep .------------------- "${outputdir}/${category}.log" ; then
        fail "output of ${category} list contained garbage"
        exit 1
    fi

    sort -u < "${outputdir}/${category}.log" | grep -F -w -v -f "${outputdir}/manual.log" | grep -E -v "${BLACKLIST}" > "${outputdir}/${category}.verbs"

    if [ ! -f "${outputdir}/${category}.verbs" ] ; then
        w_die "${outputdir}/${category}.verbs doesn't exist? No verbs to test!"
    elif [ -f "${outputdir}/${category}.verbs" ] && [ ! -s "${outputdir}/${category}.verbs" ] ; then
        w_die "${outputdir}/${category}.verbs exists, but it is empty"
    else
        echo "Testing these verbs:"
        cat "${outputdir}/${category}.verbs"
    fi

    while IFS= read -r line; do
        test_app "${line}"
    done < "${outputdir}/${category}.verbs"
}

test_dotnet()
{
    # verify that each individual installer works:
    dotnet_verbs="$(${WINETRICKS} dlls list | grep ^dotnet | grep -v -e sdk -e verifier | cut -d ' ' -f1)"
    for x in ${dotnet_verbs}; do
        echo "testing ${x}"
        test_command --verify "${x}"
    done

    # combinations that should work:
    for combo in "dotnet20 dotnet20sp2" "dotnet30 dotnet40"; do
        # shellcheck disable=SC2086
        test_command --verify ${combo}
    done

    # combinations that should break:
    # shellcheck disable=SC2043
    for fail_combo in "dotnet20sp2 dotnet20sp1" "dotnet30sp1 dotnet30"; do
        # shellcheck disable=SC2086
        EXPECT_FAIL=yes test_command ${fail_combo}
    done
}

test_listing()
{
    export _W_wine_not_needed=1
    W_WINE="${WINE}"

    # Most options should work with or without WINE available:
    for category in $("${WINETRICKS}" list); do
        export WINE="${W_WINE}"
        test_command "${category}" list

        export WINE=/dev/null
        test_command "${category}" list
    done

    for hardcoded in list list-cached list-download list-manual-download list-all; do
        export WINE="${W_WINE}"
        test_command ${hardcoded}

        export WINE=/dev/null
        test_command ${hardcoded}
    done

    # list-installed needs the installed metadata which requires wine/wineprefix info:
    export WINE="${W_WINE}"
    test_command list-installed

    # Testing list-installed without wine is hard. A new prefix won't have anything installed
    # and so winetricks bails out before it tries to run w_metadata
    # So to truly test we need something installed first, then re-use that prefix with WINE=/dev/null
    unset _W_wine_not_needed
    export WINE="${W_WINE}"
    test_command mfc42

    export WINEPREFIX="${WINE_PREFIXES}/mfc42"
    export _W_wine_not_needed=1
    export WINE=/dev/null
    EXPECT_FAIL=yes "${WINETRICKS}" -q list-installed

    unset _W_wine_not_needed
    export WINE="${W_WINE}"
}

test_prefix()
{
    # Run all this under the WINE_PREFIXES directory
    WT_TEST_PREFIX="${WINE_PREFIXES}/wt-test-prefix"
    XDG_CACHE_HOME="${WT_TEST_PREFIX}/.cache}"
    cache="${XDG_CACHE_HOME}/winetricks"

    rm -rf "${WT_TEST_PREFIX}"
    testname="test1: WINEPREFIX set, no prefix="
    if HOME="${WT_TEST_PREFIX}/home" WINEPREFIX="${WT_TEST_PREFIX}/test1" WINEDLLOVERRIDES="mshtml,mscoree=disabled" winetricks -q good && test -d "${WT_TEST_PREFIX}/test1/drive_c"; then
        pass "${testname} passed"
    else
        fail "${testname} failed"
    fi

    rm -rf "${WT_TEST_PREFIX}"
    testname="test2: WINEPREFIX not set, prefix=test2"
    if HOME="${WT_TEST_PREFIX}/home" WINEDLLOVERRIDES="mshtml,mscoree=disabled" winetricks -q prefix=test2 good && test -d "${WT_TEST_PREFIX}/home/.local/share/wineprefixes/test2/drive_c"; then
        pass "${testname} passed"
    else
        fail "${testname} failed"
    fi

    # prefix= overrules WINEPREFIX:
    rm -rf "${WT_TEST_PREFIX}"
    testname="test3: WINEPREFIX=test3, prefix=test3"
    if HOME="${WT_TEST_PREFIX}/home" WINEDLLOVERRIDES="mshtml,mscoree=disabled" WINEPREFIX="${WT_TEST_PREFIX}/test3" winetricks -q prefix=test3 good && test -d "${WT_TEST_PREFIX}/home/.local/share/wineprefixes/test3/drive_c"; then
        pass "${testname} passed"
    else
        fail "${testname} failed"
    fi

    # if both are invalid:
    # Note: if prefix= is used, ~/.local/share/wineprefixes becomes the prefix. Not sure if that's good or bad, but for now just documenting the behavior:
    rm -rf "${WT_TEST_PREFIX}"
    testname="test4: WINEPREFIX=/dev/null, prefix="
    if HOME="${WT_TEST_PREFIX}/home" WINEDLLOVERRIDES="mshtml,mscoree=disabled" WINEPREFIX="/dev/null" winetricks -q prefix= good && test -d "${WT_TEST_PREFIX}/home/.local/share/wineprefixes/drive_c"; then
        pass "${testname} passed"
    else
        fail "${testname} failed"
    fi

    # WINEPREFIX is invalid, prefix not used:
    rm -rf "${WT_TEST_PREFIX}"
    testname="test5: WINEPREFIX=/dev/null, no prefix"
    if ! HOME="${WT_TEST_PREFIX}/home" WINEDLLOVERRIDES="mshtml,mscoree=disabled" WINEPREFIX="/dev/null" winetricks -q good; then
        pass "${testname} did not succeed (as expected)"
    else
        fail "${testname} worked, should have failed"
    fi

    # if nothing is defined, ~/.wine should be used:
    rm -rf "${WT_TEST_PREFIX}"
    testname="test6: WINEPREFIX not set, prefix not set"
    unset WINEPREFIX
    if HOME="${WT_TEST_PREFIX}/home" WINEDLLOVERRIDES="mshtml,mscoree=disabled" winetricks -q good && test -d "${WT_TEST_PREFIX}/home/.wine/drive_c"; then
        pass "${testname} passed"
    else
        fail "${testname} failed"
    fi

    if [ "${W_OPT_NOCLEAN}" = "1" ]; then
        echo "W_OPT_NOCLEAN set to 1, not removing ${WT_TEST_PREFIX}"
    else
        rm -rf "${WT_TEST_PREFIX}"
    fi
}

test_quick()
{
    echo "warning: quick test takes up around 20GB"
    export W_CACHE="${cache}"

    # And test all the automatically-downloadable dlls
    test_category dlls

    # And win32 7zip fallback
    test_7zip

    # And listing verbs (with/without WINE available)
    test_listing

    # WINEPREFIX/prefix= handling
    test_prefix
}

test_full() {
    test_quick
    test_category fonts
    test_category settings
    test_dotnet
    test_speed list
    test_speed list-download
    test_speed list-cached
    test_speed list-installed
    test_custom_verbs
    test_w_compare_wine_version
    test_windows_versions
}

test_xvfb() {
    if ! command -v xvfb-run 2>/dev/null; then
        w_die "Please install xvfb-run for xvfb tests"
    fi

    BLACKLIST="${BLACKLIST}|${XVFB_BLACKLIST}"
    export BLACKLIST

    # Not test_quick() since some tests fail without proper X, but test_quick() doesn't respect $BLACKLIST
    # Also, we don't really want to duplicate those tests twice, as this is for TravisCI where time is limited..
    test_category dlls
    test_category fonts
    test_category settings
}

helper_test_w_compare_wine_version()
{
    version="$1" # version we're pretending to be (e.g., wine-2.0)
    comparison="$2" # the comparison to make (e.g., ',1.8', '1.9,', or '1.8,1.9')
    workaround_expected="$3" # should the workaround be executed (yes/no)

    WINETRICKS_WINE_VERSION="${version}"
    export WINETRICKS_WINE_VERSION

if [ "${workaround_expected}" = "yes" ] ; then
    cat > "${outputdir}/test_version.verb" <<_EOF
w_metadata test_version settings

load_test_version()
{
    # FIXME: w_info/w_die aren't getting through b/c of QUIET? stop using quiet everywhere..
    if w_workaround_wine_bug 99999 "test" "${comparison}" ; then
        w_info "Using w_workaround, expected, version: ${WINETRICKS_WINE_VERSION}"
    else
        w_die "Not using w_workaround, unexpected, version: ${WINETRICKS_WINE_VERSION}"
    fi
}
_EOF
elif [ "${workaround_expected}" = "no" ] ; then
    cat > "${outputdir}/test_version.verb" <<_EOF
w_metadata test_version settings

load_test_version()
{
    if w_workaround_wine_bug 99999 "test" "${comparison}" ; then
        w_die "Using w_workaround, unexpected, version: ${WINETRICKS_WINE_VERSION}"
    else
        w_info "Not using w_workaround, expected, version: ${WINETRICKS_WINE_VERSION}"
    fi
}
_EOF
else
    w_die "unknown value ${workaround_expected}!"
fi

    # no xvfb needed
    ${WINETRICKS} -v --no-isolate "${outputdir}/test_version.verb" ; ret=$?
    case ${ret} in
        0) pass "test_version.verb (version: ${version} comparison: ${comparison} expected: ${workaround_expected} passed" ;;
        *) fail "test_version.verb (version: ${version} comparison: ${comparison} expected: ${workaround_expected} failed" ;;
    esac
}

test_w_compare_wine_version()
{
    # Previous code was very fragile, and had no regression tests.
    # Now that we can override reported Wine version with $WINETRICKS_WINE_VERSION, easier to test using some custom verbs

    #    val1,   (for >= val1)
    #    ,val2   (for <= val2)
    #    val1,val2 (for >= val1 && <= val2)

    # helper_test_w_compare_wine_version() {
    #    version="$1" # version we're pretending to be (e.g., wine-2.0)
    #    comparison="$2" # the comparison to make (e.g., ',1.8', '1.9,', or '1.8,1.9')
    #    workaround_expected="$3" # should the workaround be executed (yes/no)

    # good (less than)
    helper_test_w_compare_wine_version "wine-1.0" ",1.8.0" "yes"
    helper_test_w_compare_wine_version "wine-3.0" ",3.0" "yes"

    # good (between)
    helper_test_w_compare_wine_version "wine-1.6" "1.4,2.0" "yes"
    helper_test_w_compare_wine_version "wine-1.8.0" "1.8.0,3.0" "yes"
    helper_test_w_compare_wine_version "wine-3.0"   "1.8.0,3.0" "yes"


    # good (greater than)
    helper_test_w_compare_wine_version "wine-1.3" "1.2," "yes"
    helper_test_w_compare_wine_version "wine-1.3.4" "1.2," "yes"
    helper_test_w_compare_wine_version "wine-1.6" "1.2," "yes"
    helper_test_w_compare_wine_version "wine-1.6-rc1" "1.2," "yes"
    helper_test_w_compare_wine_version "wine-2.0.1" "1.8.0," "yes"
    helper_test_w_compare_wine_version "wine-2.0.1" "2.0," "yes"
    helper_test_w_compare_wine_version "wine-2.0" "1.8.0," "yes"
    helper_test_w_compare_wine_version "wine-2.0" "1.8.0," "yes"
    helper_test_w_compare_wine_version "wine-2.0" "1.8.1," "yes"
    helper_test_w_compare_wine_version "wine-2.0" "1.8," "yes"
    helper_test_w_compare_wine_version "wine-2.0.1 (debian 2.0.1-r1)" "1.8.0," "yes"
    helper_test_w_compare_wine_version "wine-2.0.1 (staging)" "1.8.0," "yes"
    helper_test_w_compare_wine_version "wine-2.0-rc1" "1.8.0," "yes"
    helper_test_w_compare_wine_version "wine-3.0" "1.8.0," "yes"
    helper_test_w_compare_wine_version "wine-3.0" "3.0," "yes"

    # bad (less than)
    helper_test_w_compare_wine_version "wine-1.0" "1.8.0," "no"
    helper_test_w_compare_wine_version "wine-2.0" "3.0," "no"
    helper_test_w_compare_wine_version "wine-2.0.1 (staging)" "1.8.0," "yes"

    # bad (between)
    helper_test_w_compare_wine_version "wine-1.0" "2.0,3.0" "no"
    helper_test_w_compare_wine_version "wine-2.0" "1.4,1.6" "no"

    # bad (greater than)
    helper_test_w_compare_wine_version "wine-1.3" ",1.2" "no"
    helper_test_w_compare_wine_version "wine-1.3.4" ",1.2" "no"
    helper_test_w_compare_wine_version "wine-1.6" ",1.2" "no"
    helper_test_w_compare_wine_version "wine-1.6" ",1.2" "no"
    helper_test_w_compare_wine_version "wine-1.6-rc1" ",1.2" "no"
    helper_test_w_compare_wine_version "wine-2.0.1" ",1.8.0" "no"
    helper_test_w_compare_wine_version "wine-2.0.1" ",2.0" "no"
    helper_test_w_compare_wine_version "wine-2.0.1" ",2.0" "no"
    helper_test_w_compare_wine_version "wine-2.0" ",1.4" "no"
    helper_test_w_compare_wine_version "wine-2.0" ",1.6" "no"
    helper_test_w_compare_wine_version "wine-2.0" ",1.8.0" "no"
    helper_test_w_compare_wine_version "wine-2.0" ",1.8.0" "no"
    helper_test_w_compare_wine_version "wine-2.0" ",1.8.1" "no"
    helper_test_w_compare_wine_version "wine-2.0" ",1.8" "no"
    helper_test_w_compare_wine_version "wine-2.0" ",1.9" "no"
    helper_test_w_compare_wine_version "wine-2.0.1 (debian 2.0.1-r1)" ",1.8.0" "no"
    helper_test_w_compare_wine_version "wine-2.0.1 (staging)" ",1.8.0" "no"
    helper_test_w_compare_wine_version "wine-2.0-rc1" ",1.8.0" "no"
    helper_test_w_compare_wine_version "wine-2.0-rc1" ",1.8.0" "no"
    helper_test_w_compare_wine_version "wine-3.0" ",1.8.0" "no"
    helper_test_w_compare_wine_version "wine-4.0" ",3.0" "no"

    # FIXME: there's plenty of room for more tests..
}

test_windows_versions()
{
    # shellcheck disable=SC2013
    for version in $("${WINETRICKS}" settings list | grep 'Windows version' | cut -d ' ' -f1 | grep -v 'winver='); do
        # Make sure that changing the Windows version works:
        test_command "${version}"

        # FIXME: ideally, we'd verify the version here, but that's currently not trivial, see:
        # https://bugs.winehq.org/show_bug.cgi?id=49242
    done
}

case "$1" in
    7zip) check_deps; test_7zip;;
    check-deps) check_deps ; exit $? ;;
    custom-verbs) test_custom_verbs ;;
    dotnet) check_deps && test_dotnet ;;
    full)  check_deps && test_full ;;
    # doesn't use time/cabextract, so not bothering to check deps for now:
    listing) test_listing ;;
    prefix) test_prefix ;;
    quick) check_deps && test_quick ;;
    windows-versions) test_windows_versions;;
    w_compare_wine_version) check_deps && test_w_compare_wine_version ;;
    xvfb-check) check_deps && XVFB=xvfb-run && export XVFB && test_xvfb ;;
    *) echo "Usage: $0 quick|full" ; exit 1 ;;
esac

if [ ${errors} = 0 ] && [ ${passes} -gt 0 ] ; then
    result="PASS"
    if [ "${W_OPT_NOCLEAN}" = "1" ]; then
        echo "W_OPT_NOCLEAN set to 1, not removing ${WINE_PREFIXES}"
    else
        rm -rf "${WINE_PREFIXES}"
    fi
else
    result="FAIL"
fi
# Turn off set -x so the results are cleaner:
set +x

echo "==============================================================="
echo "Test over, ${errors} failures, ${passes} successes, ${skips} skipped."
echo ${result}
if [ ${errors} = 0 ] && [ ${passes} -gt 0 ] ; then
    echo "==============================================================="
    status=0
else
    echo "==============================================================="

    oldifs="${IFS}"
    IFS="|"

    echo "Test failures:"
    for test_failure in ${failed_verbs}; do
        if [ -z "${test_failure}" ]; then
            :
        fi
        echo "${test_failure}"
    done
    echo

    IFS=${oldifs}
    status=1
fi

# skips
if [ ${errors} = 0 ] && [ ${passes} -gt 0 ] ; then
    echo "==============================================================="
    oldifs="${IFS}"
    IFS="|"

    echo "Skipped tests:"
    for skipped_test in ${skipped_verbs}; do
        if [ -z "${skipped_test}" ]; then
            :
        fi
        echo "${skipped_test}"
    done

    IFS=${oldifs}
fi

echo "==============================================================="
exit ${status}
